﻿using System;
using System.Collections.Generic;

namespace FairyGUI
{
	public delegate void EventCallback0();
	public delegate void EventCallback1(EventContext context);

	/// <summary>
	/// 
	/// </summary>
	public class EventDispatcher : IEventDispatcher
	{
		Dictionary<string, EventBridge> _dic;

		public EventDispatcher()
		{
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="callback"></param>
		public void AddEventListener(string strType, EventCallback1 callback)
		{
			if (strType == null)
				throw new Exception("event type cant be null");

			if (_dic == null)
				_dic = new Dictionary<string, EventBridge>();

			EventBridge bridge = null;
			if (!_dic.TryGetValue(strType, out bridge))
			{
				bridge = new EventBridge(this);
				_dic[strType] = bridge;
			}
			bridge.Add(callback);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="callback"></param>
		public void AddEventListener(string strType, EventCallback0 callback)
		{
			if (strType == null)
				throw new Exception("event type cant be null");

			if (_dic == null)
				_dic = new Dictionary<string, EventBridge>();

			EventBridge bridge = null;
			if (!_dic.TryGetValue(strType, out bridge))
			{
				bridge = new EventBridge(this);
				_dic[strType] = bridge;
			}
			bridge.Add(callback);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="callback"></param>
		public void RemoveEventListener(string strType, EventCallback1 callback)
		{
			if (_dic == null)
				return;

			EventBridge bridge = null;
			if (_dic.TryGetValue(strType, out bridge))
				bridge.Remove(callback);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="callback"></param>
		public void RemoveEventListener(string strType, EventCallback0 callback)
		{
			if (_dic == null)
				return;

			EventBridge bridge = null;
			if (_dic.TryGetValue(strType, out bridge))
				bridge.Remove(callback);
		}

		/// <summary>
		/// 
		/// </summary>
		public void RemoveEventListeners()
		{
			RemoveEventListeners(null);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		public void RemoveEventListeners(string strType)
		{
			if (_dic == null)
				return;

			if (strType != null)
			{
				EventBridge bridge;
				if (_dic.TryGetValue(strType, out bridge))
					bridge.Clear();
			}
			else
			{
				foreach (KeyValuePair<string, EventBridge> kv in _dic)
					kv.Value.Clear();
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <returns></returns>
		public bool hasEventListeners(string strType)
		{
			EventBridge bridge = TryGetEventBridge(strType);
			if (bridge == null)
				return false;

			return !bridge.isEmpty;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <returns></returns>
		public bool isDispatching(string strType)
		{
			EventBridge bridge = TryGetEventBridge(strType);
			if (bridge == null)
				return false;

			return bridge._dispatching;
		}

		internal EventBridge TryGetEventBridge(string strType)
		{
			if (_dic == null)
				return null;

			EventBridge bridge = null;
			_dic.TryGetValue(strType, out bridge);
			return bridge;
		}

		internal EventBridge GetEventBridge(string strType)
		{
			if (_dic == null)
				_dic = new Dictionary<string, EventBridge>();

			EventBridge bridge = null;
			if (!_dic.TryGetValue(strType, out bridge))
			{
				bridge = new EventBridge(this);
				_dic[strType] = bridge;
			}
			return bridge;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <returns></returns>
		public bool DispatchEvent(string strType)
		{
			return DispatchEvent(strType, null);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="data"></param>
		/// <returns></returns>
		public bool DispatchEvent(string strType, object data)
		{
			return InternalDispatchEvent(strType, null, data, null);
		}

		public bool DispatchEvent(string strType, object data, object initiator)
		{
			return InternalDispatchEvent(strType, null, data, initiator);
		}

		static InputEvent sCurrentInputEvent = new InputEvent();

		internal bool InternalDispatchEvent(string strType, EventBridge bridge, object data, object initiator)
		{
			if (bridge == null)
				bridge = TryGetEventBridge(strType);

			EventBridge gBridge = null;
			if ((this is DisplayObject) && ((DisplayObject)this).gOwner != null)
				gBridge = ((DisplayObject)this).gOwner.TryGetEventBridge(strType);

			bool b1 = bridge != null && !bridge.isEmpty;
			bool b2 = gBridge != null && !gBridge.isEmpty;
			if (b1 || b2)
			{
				EventContext context = EventContext.Get();
				context.initiator = initiator != null ? initiator : this;
				context.type = strType;
				context.data = data;
				if (data is InputEvent)
					sCurrentInputEvent = (InputEvent)data;
				context.inputEvent = sCurrentInputEvent;

				if (b1)
				{
					bridge.CallCaptureInternal(context);
					bridge.CallInternal(context);
				}

				if (b2)
				{
					gBridge.CallCaptureInternal(context);
					gBridge.CallInternal(context);
				}

				EventContext.Return(context);
				context.initiator = null;
				context.sender = null;
				context.data = null;

				return context._defaultPrevented;
			}
			else
				return false;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public bool DispatchEvent(EventContext context)
		{
			EventBridge bridge = TryGetEventBridge(context.type);
			EventBridge gBridge = null;
			if ((this is DisplayObject) && ((DisplayObject)this).gOwner != null)
				gBridge = ((DisplayObject)this).gOwner.TryGetEventBridge(context.type);

			EventDispatcher savedSender = context.sender;

			if (bridge != null && !bridge.isEmpty)
			{
				bridge.CallCaptureInternal(context);
				bridge.CallInternal(context);
			}

			if (gBridge != null && !gBridge.isEmpty)
			{
				gBridge.CallCaptureInternal(context);
				gBridge.CallInternal(context);
			}

			context.sender = savedSender;
			return context._defaultPrevented;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="data"></param>
		/// <param name="addChain"></param>
		/// <returns></returns>
		internal bool BubbleEvent(string strType, object data, List<EventBridge> addChain)
		{
			EventContext context = EventContext.Get();
			context.initiator = this;

			context.type = strType;
			context.data = data;
			if (data is InputEvent)
				sCurrentInputEvent = (InputEvent)data;
			context.inputEvent = sCurrentInputEvent;
			List<EventBridge> bubbleChain = context.callChain;
			bubbleChain.Clear();

			GetChainBridges(strType, bubbleChain, true);

			int length = bubbleChain.Count;
			for (int i = length - 1; i >= 0; i--)
			{
				bubbleChain[i].CallCaptureInternal(context);
				if (context._touchCapture)
				{
					context._touchCapture = false;
					if (strType == "onTouchBegin")
						Stage.inst.AddTouchMonitor(context.inputEvent.touchId, bubbleChain[i].owner);
				}
			}

			if (!context._stopsPropagation)
			{
				for (int i = 0; i < length; ++i)
				{
					bubbleChain[i].CallInternal(context);

					if (context._touchCapture)
					{
						context._touchCapture = false;
						if (strType == "onTouchBegin")
							Stage.inst.AddTouchMonitor(context.inputEvent.touchId, bubbleChain[i].owner);
					}

					if (context._stopsPropagation)
						break;
				}

				if (addChain != null)
				{
					length = addChain.Count;
					for (int i = 0; i < length; ++i)
					{
						EventBridge bridge = addChain[i];
						if (bubbleChain.IndexOf(bridge) == -1)
						{
							bridge.CallCaptureInternal(context);
							bridge.CallInternal(context);
						}
					}
				}
			}

			EventContext.Return(context);
			context.initiator = null;
			context.sender = null;
			context.data = null;
			return context._defaultPrevented;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="data"></param>
		/// <returns></returns>
		public bool BubbleEvent(string strType, object data)
		{
			return BubbleEvent(strType, data, null);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="strType"></param>
		/// <param name="data"></param>
		/// <returns></returns>
		public bool BroadcastEvent(string strType, object data)
		{
			EventContext context = EventContext.Get();
			context.initiator = this;
			context.type = strType;
			context.data = data;
			if (data is InputEvent)
				sCurrentInputEvent = (InputEvent)data;
			context.inputEvent = sCurrentInputEvent;
			List<EventBridge> bubbleChain = context.callChain;
			bubbleChain.Clear();

			if (this is Container)
				GetChildEventBridges(strType, (Container)this, bubbleChain);
			else if (this is GComponent)
				GetChildEventBridges(strType, (GComponent)this, bubbleChain);

			int length = bubbleChain.Count;
			for (int i = 0; i < length; ++i)
				bubbleChain[i].CallInternal(context);

			EventContext.Return(context);
			context.initiator = null;
			context.sender = null;
			context.data = null;
			return context._defaultPrevented;
		}

		static void GetChildEventBridges(string strType, Container container, List<EventBridge> bridges)
		{
			EventBridge bridge = container.TryGetEventBridge(strType);
			if (bridge != null)
				bridges.Add(bridge);
			if (container.gOwner != null)
			{
				bridge = container.gOwner.TryGetEventBridge(strType);
				if (bridge != null && !bridge.isEmpty)
					bridges.Add(bridge);
			}

			int count = container.numChildren;
			for (int i = 0; i < count; ++i)
			{
				DisplayObject obj = container.GetChildAt(i);
				if (obj is Container)
					GetChildEventBridges(strType, (Container)obj, bridges);
				else
				{
					bridge = obj.TryGetEventBridge(strType);
					if (bridge != null && !bridge.isEmpty)
						bridges.Add(bridge);

					if (obj.gOwner != null)
					{
						bridge = obj.gOwner.TryGetEventBridge(strType);
						if (bridge != null && !bridge.isEmpty)
							bridges.Add(bridge);
					}
				}
			}
		}

		static void GetChildEventBridges(string strType, GComponent container, List<EventBridge> bridges)
		{
			EventBridge bridge = container.TryGetEventBridge(strType);
			if (bridge != null)
				bridges.Add(bridge);

			int count = container.numChildren;
			for (int i = 0; i < count; ++i)
			{
				GObject obj = container.GetChildAt(i);
				if (obj is GComponent)
					GetChildEventBridges(strType, (GComponent)obj, bridges);
				else
				{
					bridge = obj.TryGetEventBridge(strType);
					if (bridge != null)
						bridges.Add(bridge);
				}
			}
		}

		internal void GetChainBridges(string strType, List<EventBridge> chain, bool bubble)
		{
			EventBridge bridge = TryGetEventBridge(strType);
			if (bridge != null && !bridge.isEmpty)
				chain.Add(bridge);

			if ((this is DisplayObject) && ((DisplayObject)this).gOwner != null)
			{
				bridge = ((DisplayObject)this).gOwner.TryGetEventBridge(strType);
				if (bridge != null && !bridge.isEmpty)
					chain.Add(bridge);
			}

			if (!bubble)
				return;

			if (this is DisplayObject)
			{
				DisplayObject element = (DisplayObject)this;
				while ((element = element.parent) != null)
				{
					bridge = element.TryGetEventBridge(strType);
					if (bridge != null && !bridge.isEmpty)
						chain.Add(bridge);

					if (element.gOwner != null)
					{
						bridge = element.gOwner.TryGetEventBridge(strType);
						if (bridge != null && !bridge.isEmpty)
							chain.Add(bridge);
					}
				}
			}
			else if (this is GObject)
			{
				GObject element = (GObject)this;
				while ((element = element.parent) != null)
				{
					bridge = element.TryGetEventBridge(strType);
					if (bridge != null && !bridge.isEmpty)
						chain.Add(bridge);
				}
			}
		}
	}
}
